﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using UtilZ.Dotnet.AudioPlayer.Native;
using UtilZ.Dotnet.Ex.Audio;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.AudioPlayer.Core
{
    /// <summary>
    /// Bass解码器
    /// </summary>
    public sealed class BassStreamDecoder : IDisposable
    {
        private bool _disposed = false;
        private readonly int _handle;
        private readonly int _framSize = 256;
        private readonly long _dataLength;
        private readonly BassFFTFramSize _fftSize;
        private readonly int _chans;
        private readonly int _sampleRate;
        private readonly int _sampleBit;

        /// <summary>
        /// true:文件解码;false:内存数据解码
        /// </summary>
        private readonly bool _type;


        private readonly Stream _stream = null;
        private readonly BinaryReader _reader = null;
        private readonly STREAMPROC _streamProc = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type">true:文件;false:数据流</param>
        /// <param name="framSize"></param>
        private BassStreamDecoder(bool type, BassFFTFramSize framSize)
        {
            this._type = type;
            this._fftSize = framSize;

            Type enumAttriType = typeof(BassFFTFramSizeAttribute);
            var fields = typeof(BassFFTFramSize).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
            object[] csAttris = null;
            object enumValue = null;
            foreach (var field in fields)
            {
                enumValue = field.GetValue(null);
                if (framSize.Equals(enumValue))
                {
                    csAttris = field.GetCustomAttributes(enumAttriType, false);
                    this._framSize = ((BassFFTFramSizeAttribute)csAttris[0]).Size;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="framSize"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="FileNotFoundException"></exception>
        public BassStreamDecoder(string filePath, BassFFTFramSize framSize)
            : this(true, framSize)
        {
            if (string.IsNullOrWhiteSpace(filePath))
            {
                throw new ArgumentNullException(nameof(filePath));
            }

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("解码文件不存在", filePath);
            }

            using (var fs = File.OpenRead(filePath))
            {
                this._dataLength = fs.Length;
            }

            this._handle = BASSHelper.StreamCreateFile(filePath, 0, 0, BASSFlag.BASS_UNICODE | BASSFlag.BASS_STREAM_DECODE);
            BASS_CHANNELINFO_INTERNAL info = BASSHelper.ChannelGetInfo(this._handle);
            this._chans = info.chans;
            this._sampleRate = info.freq;
            this._sampleBit = DataSampleBits.BIT16;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="sampleRate"></param>
        /// <param name="sampleBit"></param>
        /// <param name="chans"></param>
        /// <param name="framSize"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public BassStreamDecoder(byte[] buffer, int sampleRate, int sampleBit, int chans, BassFFTFramSize framSize)
            : this(false, framSize)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            if (sampleRate < 1)
            {
                throw new ArgumentOutOfRangeException("采样率太小");
            }

            if (chans < 1)
            {
                throw new ArgumentOutOfRangeException("声道数不能小于1");
            }

            this._stream = new MemoryStream(buffer);
            this._reader = new BinaryReader(this._stream);
            this._dataLength = buffer.Length;

            this._streamProc = new STREAMPROC(this.StreamProcCallback);
            this._handle = BASSHelper.StreamCreate(sampleRate, chans, BASSFlag.BASS_UNICODE | BASSFlag.BASS_STREAM_DECODE, this._streamProc, IntPtr.Zero);
            this._chans = chans;
            this._sampleRate = sampleRate;
            this._sampleBit = sampleBit;
        }

        private byte[] _callbackData = null;
        private int StreamProcCallback(int handle, IntPtr bufferPtr, int length, IntPtr user)
        {
            try
            {
                if (this._callbackData == null || this._callbackData.Length < length)
                {
                    this._callbackData = new byte[length];
                }

                long mod = this._reader.BaseStream.Length - this._reader.BaseStream.Position;
                if (mod < length)
                {
                    length = (int)mod;
                }

                int count = this._reader.Read(this._callbackData, 0, length);
                Marshal.Copy(this._callbackData, 0, bufferPtr, count);
                return count;
            }
            catch (Exception ex)
            {
                ZLoger.Error(this._handle, null, ex, "播放器获取解码数据发生异常");
                return 0;
            }
        }







        /// <summary>
        /// 获取语谱图数据
        /// </summary>
        /// <returns></returns>
        public List<ChannelSpecData> GetSpecData()
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException(nameof(BassStreamDecoder));
            }

            int sampleBit = ((int)this._sampleBit) / 8;
            long framCount = this._dataLength / (this._framSize * sampleBit * this._chans);

            if (this._type)
            {
                return this.GetFileSpecData(framCount);
            }
            else
            {
                return this.GetBufferSpecData(framCount);
            }
        }

        private List<ChannelSpecData> GetBufferSpecData(long framCount)
        {
            throw new NotImplementedException();
        }

        private List<ChannelSpecData> GetFileSpecData(long framCount)
        {
            var channelSpecDataList = new List<ChannelSpecData>();
            for (int i = 0; i < this._chans; i++)
            {
                channelSpecDataList.Add(new ChannelSpecData());
            }

            float[] fftData = new float[this._framSize * this._chans];
            int fftSize = (int)this._fftSize;
            int sampleBit = ((int)this._sampleBit) / 8;

            if (this._chans > 1)
            {
                int index = 0;
                for (int i = 0; i < framCount; i++)
                {
                    int length = BASSHelper.ChannelGetData(this._handle, fftData, fftSize);

                    double[][] specDataArr = new double[this._chans][];
                    for (int j = 0; j < this._chans; j++)
                    {
                        specDataArr[j] = new double[this._framSize];
                    }

                    for (int k = 0; k < fftData.Length; k += this._chans)
                    {
                        for (int j = 0; j < this._chans; j++)
                        {
                            specDataArr[j][index] = 10 * Math.Log(fftData[k + j], 10);
                        }
                        index++;
                    }

                    for (int j = 0; j < this._chans; j++)
                    {
                        channelSpecDataList[j].Add(specDataArr[j]);
                    }
                }
            }
            else
            {
                double[] specData;
                for (int i = 0; i < framCount; i++)
                {
                    int length = BASSHelper.ChannelGetData(this._handle, fftData, fftSize);
                    specData = Array.ConvertAll<float, double>(fftData, delegate (float f) { return (double)(10 * Math.Log(f, 10)); });
                    channelSpecDataList[0].Add(specData);
                }
            }

            return channelSpecDataList;
        }








        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            if (this._disposed)
            {
                return;
            }

            this._disposed = true;
            BASSHelper.StreamFree(this._handle);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class ChannelSpecData
    {
        private readonly List<double[]> _dataList = new List<double[]>();

        /// <summary>
        /// 
        /// </summary>
        public List<double[]> DataList
        {
            get { return this._dataList; }
        }

        /// <summary>
        /// 
        /// </summary>
        public ChannelSpecData()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        public void Add(double[] data)
        {
            this._dataList.Add(data);
        }
    }


    /// <summary>
    /// BassFFTFramSize
    /// </summary>
    public enum BassFFTFramSize
    {
        /// <summary>
        /// 256 sample FFT (returns 128 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(256)]
        FFT256 = int.MinValue,

        /// <summary>
        /// 512 sample FFT (returns 256 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(512)]
        FFT512 = -2147483647,

        /// <summary>
        /// 1024 sample FFT (returns 512 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(1024)]
        FFT1024 = -2147483646,

        /// <summary>
        /// 2048 sample FFT (returns 1024 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(2048)]
        FFT2048 = -2147483645,

        /// <summary>
        /// 4096 sample FFT (returns 2048 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(4096)]
        FFT4096 = -2147483644,

        /// <summary>
        /// 8192 sample FFT (returns 4096 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(8192)]
        FFT8192 = -2147483643,

        /// <summary>
        /// 16384 sample FFT (returns 8192 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(16384)]
        FFT16384 = -2147483642,

        /// <summary>
        /// 32768 sample FFT (returns 16384 floating-point values)
        /// </summary>
        [BassFFTFramSizeAttribute(32768)]
        FFT32768 = -2147483641,
    }

    internal class BassFFTFramSizeAttribute : Attribute
    {
        public int Size { get; private set; }

        public BassFFTFramSizeAttribute(int size)
        {
            this.Size = size;
        }
    }
}
